package com.config.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

@Slf4j
public class CustomRealm extends JdbcRealm {
// public class CustomRealm extends AuthorizingRealm {

    /**
     * 身份（登录）认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();

        // Null username is invalid
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }

        Connection conn = null;
        AuthenticationInfo info = null;
        try {
            conn = dataSource.getConnection();
            // 获取用户信息
            String password = getPasswordForUser(conn, username);
            if (password == null) {
                throw new UnknownAccountException("No account found for user [" + username + "]");
            }
            // 密码比对由 Shiro 完成
            SimpleAuthenticationInfo saInfo = new SimpleAuthenticationInfo(username, password, getName());
            /**
             * This (very bad) example uses the username as the salt in this sample app.  DON'T DO THIS IN A REAL APP!
             *
             * Salts should not be based on anything that a user could enter (attackers can exploit this).  Instead
             * they should ideally be cryptographically-strong randomly generated numbers.
             */
            // 盐值加密
            saInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
            info = saInfo;
        } catch (SQLException e) {
            final String message = "There was a SQL error while authenticating user [" + username + "]";
            if (log.isErrorEnabled()) {
                log.error(message, e);
            }
            // Rethrow any SQL errors as an authentication exception
            throw new AuthenticationException(message, e);
        } finally {
            JdbcUtils.closeConnection(conn);
        }
        return info;
    }


    private String getPasswordForUser(Connection conn, String username) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        String password = null;
        try {
            ps = conn.prepareStatement(authenticationQuery);
            ps.setString(1, username);
            rs = ps.executeQuery();
            // 循环显示结果-尽管我们只希望得到一个结果，因为用户名应该是唯一的
            boolean foundResult = false;
            while (rs.next()) {
                // 检查以确保仅处理一行
                if (foundResult) {
                    throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
                }
                password = rs.getString(1);
                foundResult = true;
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }
        return password;
    }


    /**
     * 角色以及权限认证
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Subject subject = SecurityUtils.getSubject();

        // 是否已登陆
        if (!subject.isAuthenticated()) {
            throw new AuthenticationException("未登陆");
        }

        Object principal = principalCollection.getPrimaryPrincipal();
        Optional.ofNullable(principal).orElseThrow(() -> new AuthorizationException("未被授权"));

        if (log.isInfoEnabled()) {
            log.info(String.format("start doGetAuthorizationInfo,user principal[%s]", principal));
        }

        Set<String> roles = new HashSet<>();
        roles.add("user");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
        info.addRole("admin");
        info.addStringPermission("*:*:*");
        return info;
    }
}
